﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace MetalSoda
{
    /// <summary>
    /// Provides SQL Server instance properties
    /// </summary>
    [DebuggerDisplay("{Display}")]
    [Serializable]
    public class ServerInfo
    {
        #region Nested Types
        [Serializable]
        class ServiceConstants
        {
            public readonly string ServerName;
            public readonly string ServiceName;
            public readonly string Login;
            public readonly string SSBEndPointName;
            public readonly int SSBPortNumber;
            public readonly string ConnectionString;

            public ServiceConstants(System.Data.IDataRecord record, InstanceSetupInfo setup)
            {
                object obj;
                Login = (string)record["Login"];
                ServerName = (string)record["ServerName"];
                ServiceName = (string)record["ServiceName"];
                SSBEndPointName = (obj = record["SSBEndPointName"]) == DBNull.Value ? setup.SSBEndPointName : obj.ToString();
                SSBPortNumber = (obj = record["SSBPortNumber"]) == DBNull.Value ? setup .SSBEndPointPort : (int)obj;
                string dataSource = ServerName;
                if (false == ServiceName.Equals("MSSQLSERVER", StringComparison.OrdinalIgnoreCase))
                {
                    dataSource = string.Format(@"{0}\{1}", ServerName, ServiceName);
                }
                ConnectionString = new SqlConnectionStringBuilder { DataSource = dataSource, InitialCatalog = "master", IntegratedSecurity = true }.ConnectionString;
            }
        } 
        #endregion

        #region Private Fields

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        readonly OneToManyAssociation<ServerInfo, DatabaseInfo> m_databases;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        readonly LazyObject<ServiceConstants> ConstantsLazyObject;

        readonly InstanceSetupInfo m_setup;

        #endregion       

        #region Properties

        
        public InstanceSetupInfo Setup { [DebuggerStepThrough] get { return m_setup; } }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public string Display { get { return string.Format(@"{0}\{1}, Databases: {2}", ServerName, InstanceName, m_databases.Count ); } }

        /// <summary>
        /// 
        /// </summary>
        public string MasterDBConnectionString { get { return ConstantsLazyObject.Value.ConnectionString; } }

        /// <summary>
        /// 
        /// </summary>
        public string ServerName { get { return ConstantsLazyObject.Value.ServerName; } }

        /// <summary>
        /// 
        /// </summary>
        public string InstanceName { get { return ConstantsLazyObject.Value.ServiceName; } }

        /// <summary>
        /// 
        /// </summary>
        public string Login { get { return ConstantsLazyObject.Value.Login; } }

        /// <summary>
        /// 
        /// </summary>
        public int SSBEndPointPort { get { return ConstantsLazyObject.Value.SSBPortNumber; } }

        /// <summary>
        /// 
        /// </summary>
        public string SSBEndPointName { get { return ConstantsLazyObject.Value.SSBEndPointName; } }

        /// <summary>
        /// 
        /// </summary>
        public string SSBEndPointAddress { get { return string.Format("tcp://{0}:{1}", ServerName, SSBEndPointPort); } }

        /// <summary>
        /// 
        /// </summary>
        public IEnumerable<DatabaseInfo> Databases { get { return m_databases.AsEnumerable(); } } 

        #endregion       
        
        #region Constructors

        public ServerInfo(string name, InstanceSetupInfo setup)
        {
            if (name == null) { throw new ArgumentNullException("name"); }

            m_databases = new OneToManyAssociation<ServerInfo, DatabaseInfo>(this,
                added: args => args.Ref.Server = this,
                removed: args => args.Ref.Server = null);

            m_setup = setup;
            ConstantsLazyObject = new LazyObject<ServiceConstants>(() => RetrieveInstanceConstants(name));  
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        public ServerInfo(string name = "localhost") : this(name, new InstanceSetupInfo()) {} 

        #endregion

        #region Methods

        public DatabaseInfo this[string databaseName, bool autoCreate = false] { get { return Database(databaseName, autoCreate); } }

        public DatabaseInfo Database(string databaseName, bool autoCreate = false)
        {
            if (string.IsNullOrWhiteSpace(databaseName)) { throw new ArgumentException("databaseName"); }
            databaseName = DatabaseInfo.NormalizeDatabaseName(databaseName);
            var db = m_databases.Where(i => i.DatabaseName.Equals(databaseName, StringComparison.OrdinalIgnoreCase)).SingleOrDefault();
            if (false == Object.ReferenceEquals(db, null)) { return db; }
            db = new DatabaseInfo(databaseName, this, autoCreate);
            IncludeDatabase(db);
            return db;
        }

        public bool ExcludeDatabase(string databaseName)
        {
            if (string.IsNullOrWhiteSpace(databaseName)) { return false; }
            databaseName = DatabaseInfo.NormalizeDatabaseName(databaseName);
            var db = m_databases.Where(i => i.DatabaseName.Equals(databaseName, StringComparison.OrdinalIgnoreCase)).SingleOrDefault();
            return m_databases.Remove(db);
        } 
        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="database"></param>
        /// <returns></returns>
        internal ServerInfo IncludeDatabase(DatabaseInfo database)
        {
            var duplicates =
                from db in m_databases
                where db.DatabaseName.Equals(database.DatabaseName, StringComparison.OrdinalIgnoreCase)
                where Object.ReferenceEquals(db, database) == false
                select db;
            if (duplicates.Any())
            {
                throw new InvalidOperationException();
            }
            m_databases.Add(database);
            return this;
        }

        /// <summary>
        /// 
        /// </summary>
        internal void DeployInstanceConfigurations()
        {
            using (var connection = new SqlConnection(this.MasterDBConnectionString))
            {
                connection.Open();
                var command = new SqlCommand(new SQLScript.DeployInstanceConfigurationsRuntimeTextTemplate(this).TransformText(), connection);
                command.ExecuteNonQuery();
            }
        }

        #region Private Methods

        private ServiceConstants RetrieveInstanceConstants(string dataSource)
        {
            string connectionString = new SqlConnectionStringBuilder() { DataSource = dataSource, InitialCatalog = "master", IntegratedSecurity = true }.ConnectionString;
            using (var connection = new SqlConnection(connectionString))
            {
                try { connection.Open(); }
                catch (SqlException ex)
                {
                    var message = new StringBuilder()
                    .AppendFormat("Failed to open connection to the master database on [{0}]. Ensure that the [{0}] server is accessible.", dataSource);
                    throw new MetalSodaException(message.ToString(), ex);
                }
                var command = new SqlCommand(new SQLScript.SelectServerConstantsRuntimeTextTemplate().TransformText(), connection);
                using (var reader = command.ExecuteReader())
                {
                    object obj;
                    reader.Read();
                    return new ServiceConstants(reader, m_setup);
                }
            }
        } 

        #endregion
    }
}
